package net.scribblemedia.candle.mlengine.training;

import static net.scribblemedia.candle.util.ScribbleCollectionUtils.asList;
import static net.scribblemedia.candle.util.ScribbleRandomUtils.randomDateMidnightBetween;
import static net.scribblemedia.candle.util.ScribbleRandomUtils.randomDoubleBetween;
import static net.scribblemedia.candle.util.ScribbleRandomUtils.randomIntegerBetween;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import net.scribblemedia.candle.data.download.MarketDataDownloader;
import net.scribblemedia.candle.model.LayerConfiguration;
import net.scribblemedia.candle.model.TickerSymbol;
import net.scribblemedia.candle.model.TrainableNetwork;
import net.scribblemedia.candle.model.TrainingPlan;

import org.apache.commons.io.IOUtils;
import org.encog.engine.network.activation.ActivationLOG;
import org.encog.engine.network.activation.ActivationSigmoid;
import org.encog.engine.network.activation.ActivationSteepenedSigmoid;
import org.encog.ml.data.market.loader.LoadedMarketData;
import org.joda.time.DateMidnight;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class CreateTrainingData {

	private static final int NUMBER_OF_TRAINING_PLANS_PER_NETWORK = 1000;
	private static final int NUMBER_OF_NETWORKS = 1000;
	
	public static void main(String[] args) throws IOException {
		new ClassPathXmlApplicationContext("classpath*:META-INF/spring/applicationContext*.xml");
		
		loadTickerSymbolsIfNecessary();
		
		NetworkTrainingBean networkTrainingBean = new NetworkTrainingBean();
		networkTrainingBean.setActivationFunctions(asList(new ActivationSigmoid(), new ActivationLOG(), new ActivationSteepenedSigmoid()));
		networkTrainingBean.setEarliestTrainingDate(new DateMidnight(2001, 01, 01));
		networkTrainingBean.setLatestTrainingDate(new DateMidnight(2008, 01, 01));
		networkTrainingBean.setMaxEndOfNoseGainPeriodDays(5);
		networkTrainingBean.setMinEndOfNoseGainPeriodDays(5);
		networkTrainingBean.setMaxFlatGainThresholdPercentage(30);
		networkTrainingBean.setMinFlatGainThresholdPercentage(20);
		networkTrainingBean.setMaxHiddenLayers(10);
		networkTrainingBean.setMinHiddenLayers(2);
		networkTrainingBean.setMaxNeuronsPerLayer(1000);
		networkTrainingBean.setMinNeuronsPerLayer(1);
		networkTrainingBean.setMaxNoseSizeDays(30);
		networkTrainingBean.setMinNoseSizeDays(30);
		networkTrainingBean.setMaxTailSizeDays(60);
		networkTrainingBean.setMinTailSizeDays(60);
		networkTrainingBean.setMaxTrainingSymbols(50);
		networkTrainingBean.setMinTrainingSymbols(5);
		networkTrainingBean.setMinMaxErrorRatePercentage(1);
		networkTrainingBean.setMinMaxErrorRatePercentage(25);
		
		createPersistentNetworks(NUMBER_OF_NETWORKS, networkTrainingBean);
	}

	private static void createPersistentNetworks(int numberOfNetworksToCreate, NetworkTrainingBean networkTrainingBean) {
		List<TrainingPlan> trainingPlans = createTrainingPlans(NUMBER_OF_TRAINING_PLANS_PER_NETWORK, networkTrainingBean);
		
		for (int networkCount = 0; networkCount < numberOfNetworksToCreate; networkCount++) {
			TrainableNetwork trainableNetwork = new TrainableNetwork();
			
			trainableNetwork.persist();
			trainableNetwork = TrainableNetwork.findTrainableNetwork(trainableNetwork.getId());
			
			int numberOfHiddenLayers = randomIntegerBetween(networkTrainingBean.getMinHiddenLayers(), networkTrainingBean.getMaxHiddenLayers());
			trainableNetwork.setLayers(new ArrayList<LayerConfiguration>());
			
			for (int hiddenLayerIndex = 0; hiddenLayerIndex < numberOfHiddenLayers; hiddenLayerIndex++) {
				LayerConfiguration layerConfiguration = new LayerConfiguration();
				ActivationFunctionType[] activationFunctionTypes = ActivationFunctionType.values();
				layerConfiguration.setActivationFunctionClassName(activationFunctionTypes[randomIntegerBetween(0, activationFunctionTypes.length - 1)].getClazz().getName());
				layerConfiguration.setBias(true);
				layerConfiguration.setNeuronCount(randomIntegerBetween(networkTrainingBean.getMinNeuronsPerLayer(), networkTrainingBean.getMaxNeuronsPerLayer()));
				
				layerConfiguration.setTrainableNetwork(trainableNetwork);
				trainableNetwork.addLayer(layerConfiguration);
			}
			
			trainableNetwork.merge();
			
			trainingPlans = TrainingPlan.findAllTrainingPlans();
			trainableNetwork = TrainableNetwork.findTrainableNetwork(trainableNetwork.getId());
			
			for (TrainingPlan trainingPlan : trainingPlans) {
				trainableNetwork.addTrainingPlan(trainingPlan);
			}
			
			trainableNetwork.merge();
		}
	}
	
	private static List<TrainingPlan> createTrainingPlans(int numberOfTrainingSetsPerNetwork, NetworkTrainingBean networkTrainingBean) {
		List<TrainingPlan> trainingPlans = new ArrayList<TrainingPlan>();
		for (int trainingPlanIndex = 0; trainingPlanIndex < numberOfTrainingSetsPerNetwork; trainingPlanIndex++) {
			TrainingPlan trainingPlan = new TrainingPlan();
			trainingPlan.setEndOfNoseGainPeriodDays(randomIntegerBetween(networkTrainingBean.getMinEndOfNoseGainPeriodDays(), networkTrainingBean.getMaxEndOfNoseGainPeriodDays()));
			trainingPlan.setFlatGainThresholdPercentage(randomDoubleBetween(networkTrainingBean.getMinFlatGainThresholdPercentage(), networkTrainingBean.getMaxFlatGainThresholdPercentage()));
			trainingPlan.setMaxErrorRate(randomDoubleBetween(networkTrainingBean.getMinMaxErrorRatePercentage(), networkTrainingBean.getMaxMaxErrorRatePercentage()));
			trainingPlan.setMinimumGainPercentage(randomDoubleBetween(networkTrainingBean.getMinMinimumGainPercentage(), networkTrainingBean.getMaxMinimumGainPercentage()));
			trainingPlan.setNoseSizeDays(randomIntegerBetween(networkTrainingBean.getMinNoseSizeDays(), networkTrainingBean.getMaxNoseSizeDays()));
			trainingPlan.setTailSizeDays(randomIntegerBetween(networkTrainingBean.getMinTailSizeDays(), networkTrainingBean.getMaxTailSizeDays()));
			trainingPlan.setTrainingPeriodEndDate(randomDateMidnightBetween(networkTrainingBean.getEarliestTrainingDate(), networkTrainingBean.getLatestTrainingDate()).toDate());
			trainingPlan.setTrainingPeriodStartDate(randomDateMidnightBetween(networkTrainingBean.getEarliestTrainingDate(), new DateMidnight(trainingPlan.getTrainingPeriodEndDate())).toDate());
			
			trainingPlan.persist();
			
			trainingPlan = TrainingPlan.findTrainingPlan(trainingPlan.getId());
			
			List<TickerSymbol> tickerSymbols = getRandomTickerSymbols(randomIntegerBetween(networkTrainingBean.getMinTrainingSymbols(), networkTrainingBean.getMaxTrainingSymbols()));
			
			for (TickerSymbol tickerSymbol : tickerSymbols) {
				trainingPlan.addTickerSymbol(tickerSymbol);
			}
			
			trainingPlan.merge();
			
			trainingPlans.add(trainingPlan);
		}
		
		return trainingPlans;
		
		/*
		
		Collection<LoadedMarketData> marketData = new ArrayList<LoadedMarketData>();
		
		List<TickerSymbol> ticketSymbols = getRandomTickerSymbols(randomIntegerBetween(networkTrainingBean.getMinTrainingSymbols(), networkTrainingBean.getMaxTrainingSymbols()));
		
		
		for (TickerSymbol symbol : ticketSymbols) {
			addMarketDataToTrainingSet(marketData, symbol.getTickerSymbol());
		}
		
		Collection<PatternWindow> patternWindows = new PatternWindowFactory().createPatternWindows(new ArrayList<LoadedMarketData>(marketData), randomIntegerBetween(networkTrainingBean.getMinTailSizeDays(), networkTrainingBean.getMaxTailSizeDays()), randomIntegerBetween(networkTrainingBean.getMinNoseSizeDays(), networkTrainingBean.getMaxNoseSizeDays()));

		TrainingSetHolder trainingSetHolder = new PatternWindowService().buildTrainingSet(patternWindows, (double) randomIntegerBetween((int) networkTrainingBean.getMinMinimumGainPercentage(), (int) networkTrainingBean.getMaxMinimumGainPercentage()), randomIntegerBetween(networkTrainingBean.getMinEndOfNoseGainPeriodDays(), networkTrainingBean.getMaxEndOfNoseGainPeriodDays()), (double) randomIntegerBetween((int) networkTrainingBean.getMinFlatGainThresholdPercentage(), (int) networkTrainingBean.getMaxFlatGainThresholdPercentage()));
		*/
	}
	
	private static List<TickerSymbol> getRandomTickerSymbols(int numberOfTickerSymbols) {
		List<TickerSymbol> tickerSymbols = TickerSymbol.findAllTickerSymbols();
		List<TickerSymbol> subsetOfTickerSymbols = new ArrayList<TickerSymbol>();
		Collections.shuffle(tickerSymbols);
		for (int i = 0; i < numberOfTickerSymbols; i++) {
			subsetOfTickerSymbols.add(tickerSymbols.get(i));
		}
		return subsetOfTickerSymbols;
	}

	private static void addMarketDataToTrainingSet(Collection<LoadedMarketData> marketData, String symbol) {
		marketData.addAll(new MarketDataDownloader(new DateMidnight(2010,1,1).toDate(), new DateMidnight(2012,8,20).toDate(), symbol).retrieveMarketData());
	}

	private static void loadTickerSymbolsIfNecessary() throws IOException {
		InputStream inputStream = CreateTrainingData.class.getResourceAsStream("/ticker_symbols.txt");
		StringWriter writer = new StringWriter();
		IOUtils.copy(inputStream, writer, "UTF-8");
		String[] symbols = writer.toString().split("\n");
		for (String symbol : symbols) {
			List<TickerSymbol> tickerSymbols = TickerSymbol.findTickerSymbolsByTickerSymbolEquals(symbol).getResultList();
			if (tickerSymbols.isEmpty()) {
				TickerSymbol tickerSymbol = new TickerSymbol();
				tickerSymbol.setTickerSymbol(symbol);
				tickerSymbol.persist();
			}
		}
	}
}
